use std::{
    fs::{self, Metadata}, path::PathBuf,
};

use crate::utils::{Option,OptionSyncMode};

pub trait Events {
    fn on_join(&mut self);
    fn on_update(&mut self);
    fn on_remove(&mut self);
    fn on_push(&mut self);
    fn on_error(&mut self, msg:String);
}

fn is_same_meta(meta_a:&Metadata, meta_b:&Metadata) -> bool {
    if meta_a.len() != meta_b.len() {return false}
    if meta_a.modified().unwrap() > meta_b.modified().unwrap() {return false}
    true
}

fn copy_file(path_from:&PathBuf, path_to:&PathBuf, eventp:&mut impl Events) -> bool {
    match fs::copy(path_from, path_to) {
        Ok(_) => {true}
        Err(e) => {
            eventp.on_error(format!("file {:?} can't copy to {:?}: {}", path_from, path_to, e));
            false
        }
    }
}

fn copy_dir(dir_from:&PathBuf, dir_to:&PathBuf, eventp:&mut impl Events) -> bool {
    match fs::read_dir(dir_from) {
        Ok(items) => {
            if ! dir_to.is_dir() {
                if let Err(e) = fs::create_dir(dir_to) {
                    eventp.on_error(format!("dir {:?} can't create: {}", dir_to, e));
                    return false;
                }
            }
            for item in items {
                let path_from = item.unwrap().path();
                let path_to = dir_to.join(path_from.file_name().unwrap());
                copy_file(&path_from, &path_to, eventp);
            }
            true
        }
        Err(e) => {
            eventp.on_error(format!("dir {:?} can't copy to {:?}: {}", dir_from, dir_to, e));
            false
        }
    }
}

fn sync_file(path_from:&PathBuf, path_to:&PathBuf, opt:&Option, eventp:&mut impl Events) {
    if path_to.is_file() {
        let meta_from = fs::metadata(path_from).unwrap();
        let meta_to = fs::metadata(path_to).unwrap();
        if is_same_meta(&meta_from, &meta_to) {
            if opt.use_hash_diff_file {}
        } else {
            if copy_file(path_from, path_to, eventp) {
                eventp.on_update();
            }
        }
    } else {
        if copy_file(path_from, path_to, eventp) {
            eventp.on_join();
        }
    }
}

fn mode_auto_remove(path_to:&PathBuf, eventp:&mut impl Events) {
    if path_to.is_file() {
        if let Err(e) = fs::remove_file(path_to) {
            eventp.on_error(format!("file {:?} can't remove: {}", path_to, e));
        } else {eventp.on_remove()}
    } else if path_to.is_dir() {
        if let Err(e) = fs::remove_dir_all(path_to) {
            eventp.on_error(format!("dir {:?} can't remove: {}", path_to, e));
        } else {eventp.on_remove()}
    }
}

fn mode_push_to_from(path_from:&PathBuf, path_to:&PathBuf, eventp:&mut impl Events) {
    if path_to.is_dir() {
        let dir_from = path_from.join(path_to.file_name().unwrap());
        if copy_dir(path_to, &dir_from, eventp) {
            eventp.on_push()
        }
        eventp.on_push();
    } else if path_to.is_file() {
        if copy_file(path_to, path_from, eventp) {
            eventp.on_push();
        }
    }
}

pub fn fsync(dir_from:&PathBuf, dir_to:&PathBuf, opt:&Option, eventp:&mut impl Events) {
    match fs::read_dir(dir_from) {
        Ok(items) => {
            for item in items {
                let path_from = item.unwrap().path();
                let path_to = dir_to.join(path_from.file_name().unwrap());
                if path_from.is_file() {
                    sync_file(&path_from, &path_to, opt, eventp);
                } else if path_from.is_dir() && opt.allow_recurive_dir {
                    if ! path_to.is_dir() {
                        if let Err(e) = fs::create_dir(&path_to) {
                            eventp.on_error(format!("dir {:?} can't create: {}", path_to, e));
                            return
                        }
                    }
                    fsync(&path_from, &path_to, opt, eventp);
                }
            }
            let items = fs::read_dir(dir_to).unwrap();
            match opt.sync_mode {
                OptionSyncMode::AutoRemove => {
                    for item in items {
                        let path_to = item.unwrap().path();
                        let path_from = dir_from.join(path_to.file_name().unwrap());
                        if ! path_from.exists() {
                            mode_auto_remove(&path_to, eventp)
                        }
                    }
                }
                OptionSyncMode::PushToFrom => {
                    for item in items {
                        let path_to = item.unwrap().path();
                        let path_from = dir_from.join(path_to.file_name().unwrap());
                        if ! path_from.exists() {
                            mode_push_to_from(&path_from, &path_to, eventp)
                        }
                    }
                }
                OptionSyncMode::Default => {}
            }
        }
        Err(e) => {
            eventp.on_error(format!("dir {:?} can't read: {}", dir_from, e));
        }
    }
}

